<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"
"http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<title>WinLIRC Client</title>
<meta http-equiv="Content-Type" content="text/html; charset=iso-8859-1">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<link href="../static/theme.css" rel="stylesheet" type="text/css" />
<script src="../static/content.js" type="text/javascript"></script>
<meta name="description" content="This open-source WinLIRC client can perform keystrokes, mouse clicks, and other actions in response to buttons you press on your remote control.">
</head>
<body>

<h1>WinLIRC Client</h1>

<p>This script receives notifications from <a href="http://winlirc.sourceforge.net">WinLIRC</a> whenever you press
 a button on your remote control. It can be used to automate Winamp,
 Windows Media Player, etc. It's easy to configure. For example, if
 WinLIRC recognizes a button named &quot;VolUp&quot; on your remote control,
 create a label named VolUp and beneath it use the command
 <code>SoundSet +5</code> to increase the soundcard's volume by 5%.
</p>
<p><a href="WinLIRC.ahk">Download This Script</a> &nbsp;| &nbsp;<a href="index.htm">Other Sample Scripts</a> &nbsp;| &nbsp;<a href="../AutoHotkey.htm">Home</a></p>

<pre class="NoIndent"><em>; Here are the steps to use this script:
; 1) Configure WinLIRC to recognize your remote control and its buttons.
;    WinLIRC is at <a href="http://winlirc.sourceforge.net">http://winlirc.sourceforge.net</a>
; 2) Edit the WinLIRC path, address, and port in the CONFIG section below.
; 3) Launch this script. It will start the WinLIRC server if needed.
; 4) Press some buttons on your remote control. A small window will
;    appear showing the name of each button as you press it.
; 5) Configure your buttons to send keystrokes and mouse clicks to
;    windows such as Winamp, Media Player, etc. See the examples below.</em>

<em>; This script requires AutoHotkey 1.0.38.04 or later.
; HISTORY OF CHANGES
; March 2, 2007:
; - Improved reliability via &quot;Critical&quot; in ReceiveData().
; October 5, 2005:
; - Eliminated Winsock warning dialog &quot;10054&quot; upon system shutdown/logoff.
; - Added option &quot;DelayBetweenButtonRepeats&quot; to throttle the repeat speed.</em>

<em>; -------------------------------------------------
; CONFIGURATION SECTION: Set your preferences here.
; -------------------------------------------------
; Some remote controls repeat the signal rapidly while you're holding down
; a button. This makes it difficult to get the remote to send only a single
; signal. The following setting solves this by ignoring repeated signals
; until the specified time has passed. 200 is often a good setting.  Set it
; to 0 to disable this feature.</em>
DelayBetweenButtonRepeats = 200

<em>; Specify the path to WinLIRC, such as C:\WinLIRC\winlirc.exe</em>
WinLIRC_Path = %A_ProgramFiles%\WinLIRC\winlirc.exe

<em>; Specify WinLIRC's address and port. The most common are 127.0.0.1 (localhost) and 8765.</em>
WinLIRC_Address = 127.0.0.1
WinLIRC_Port = 8765

<em>; Do not change the following two lines. Skip them and continue below.</em>
Gosub WinLIRC_Init
return

<em>; --------------------------------------------
; ASSIGN ACTIONS TO THE BUTTONS ON YOUR REMOTE
; --------------------------------------------
; Configure your remote control's buttons below. Use WinLIRC's names
; for the buttons, which can be seen in your WinLIRC config file
; (.cf file) -- or you can press any button on your remote and the
; script will briefly display the button's name in a small window.
; 
; Below are some examples. Feel free to revise or delete them to suit
; your preferences.</em>

VolUp:
SoundSet +5  <em>; Increase master volume by 5%. On Vista, replace this line with: Send {Volume_Up}</em>
return

VolDown:
SoundSet -5  <em>; Reduce master volume by 5%. On Vista, replace this line with: Send {Volume_Down}</em>
return

ChUp:
WinGetClass, ActiveClass, A
if ActiveClass in Winamp v1.x,Winamp PE  <em>; Winamp is active.</em>
    Send {right}  <em>; Send a right-arrow keystroke.</em>
else  <em>; Some other type of window is active.</em>
    Send {WheelUp}  <em>; Rotate the mouse wheel up by one notch.</em>
return

ChDown:
WinGetClass, ActiveClass, A
if ActiveClass in Winamp v1.x,Winamp PE  <em>; Winamp is active.</em>
    Send {left}  <em>; Send a left-arrow keystroke.</em>
else  <em>; Some other type of window is active.</em>
    Send {WheelDown}  <em>; Rotate the mouse wheel down by one notch.</em>
return

Menu:
IfWinExist, Untitled - Notepad
{
    WinActivate
}
else
{
    Run, Notepad
    WinWait, Untitled - Notepad
    WinActivate
}
Send Here are some keystrokes sent to Notepad.{Enter}
return

<em>; The examples above give a feel for how to accomplish common tasks.
; To learn the basics of AutoHotkey, check out the Quick-start Tutorial
; at <a href="http://www.autohotkey.com/docs/Tutorial.htm">http://www.autohotkey.com/docs/Tutorial.htm</a></em>

<em>; ----------------------------
; END OF CONFIGURATION SECTION
; ----------------------------
; Do not make changes below this point unless you want to change the core
; functionality of the script.</em>

WinLIRC_Init:
OnExit, ExitSub  <em>; For connection cleanup purposes.</em>

<em>; Launch WinLIRC if it isn't already running:</em>
Process, Exist, winlirc.exe
if not ErrorLevel  <em>; No PID for WinLIRC was found.</em>
{
    IfNotExist, %WinLIRC_Path%
    {
        MsgBox The file &quot;%WinLIRC_Path%&quot; does not exist. Please edit this script to specify its location.
        ExitApp
    }
    Run %WinLIRC_Path%
    Sleep 200  <em>; Give WinLIRC a little time to initialize (probably never needed, just for peace of mind).</em>
}

<em>; Connect to WinLIRC (or any type of server for that matter):</em>
socket := ConnectToAddress(WinLIRC_Address, WinLIRC_Port)
if socket = -1  <em>; Connection failed (it already displayed the reason).</em>
    ExitApp

<em>; Find this script's main window:</em>
Process, Exist  <em>; This sets ErrorLevel to this script's PID (it's done this way to support compiled scripts).</em>
DetectHiddenWindows On
ScriptMainWindowId := WinExist(&quot;ahk_class AutoHotkey ahk_pid &quot; . ErrorLevel)
DetectHiddenWindows Off

<em>; When the OS notifies the script that there is incoming data waiting to be received,
; the following causes a function to be launched to read the data:</em>
NotificationMsg = 0x5555  <em>; An arbitrary message number, but should be greater than 0x1000.</em>
OnMessage(NotificationMsg, &quot;ReceiveData&quot;)

<em>; Set up the connection to notify this script via message whenever new data has arrived.
; This avoids the need to poll the connection and thus cuts down on resource usage.</em>
FD_READ = 1     <em>; Received when data is available to be read.</em>
FD_CLOSE = 32   <em>; Received when connection has been closed.</em>
if DllCall(&quot;Ws2_32\WSAAsyncSelect&quot;, &quot;UInt&quot;, socket, &quot;UInt&quot;, ScriptMainWindowId, &quot;UInt&quot;, NotificationMsg, &quot;Int&quot;, FD_READ|FD_CLOSE)
{
    MsgBox % &quot;WSAAsyncSelect() indicated Winsock error &quot; . DllCall(&quot;Ws2_32\WSAGetLastError&quot;)
    ExitApp
}
return



ConnectToAddress(IPAddress, Port)
<em>; This can connect to most types of TCP servers, not just WinLIRC.
; Returns -1 (INVALID_SOCKET) upon failure or the socket ID upon success.</em>
{
    VarSetCapacity(wsaData, 400)
    result := DllCall(&quot;Ws2_32\WSAStartup&quot;, &quot;UShort&quot;, 0x0002, &quot;UInt&quot;, &amp;wsaData) <em>; Request Winsock 2.0 (0x0002)</em>
    <em>; Since WSAStartup() will likely be the first Winsock function called by this script,</em>
    <em>; check ErrorLevel to see if the OS has Winsock 2.0 available:</em>
    if ErrorLevel
    {
        MsgBox WSAStartup() could not be called due to error %ErrorLevel%. Winsock 2.0 or higher is required.
        return -1
    }
    if result  <em>; Non-zero, which means it failed (most Winsock functions return 0 upon success).</em>
    {
        MsgBox % &quot;WSAStartup() indicated Winsock error &quot; . DllCall(&quot;Ws2_32\WSAGetLastError&quot;)
        return -1
    }

    AF_INET = 2
    SOCK_STREAM = 1
    IPPROTO_TCP = 6
    socket := DllCall(&quot;Ws2_32\socket&quot;, &quot;Int&quot;, AF_INET, &quot;Int&quot;, SOCK_STREAM, &quot;Int&quot;, IPPROTO_TCP)
    if socket = -1
    {
        MsgBox % &quot;socket() indicated Winsock error &quot; . DllCall(&quot;Ws2_32\WSAGetLastError&quot;)
        return -1
    }

    <em>; Prepare for connection:</em>
    SizeOfSocketAddress = 16
    VarSetCapacity(SocketAddress, SizeOfSocketAddress)
    InsertInteger(2, SocketAddress, 0, AF_INET)   <em>; sin_family</em>
    InsertInteger(DllCall(&quot;Ws2_32\htons&quot;, &quot;UShort&quot;, Port), SocketAddress, 2, 2)   <em>; sin_port</em>
    InsertInteger(DllCall(&quot;Ws2_32\inet_addr&quot;, &quot;AStr&quot;, IPAddress), SocketAddress, 4, 4)   <em>; sin_addr.s_addr</em>

    <em>; Attempt connection:</em>
    if DllCall(&quot;Ws2_32\connect&quot;, &quot;UInt&quot;, socket, &quot;UInt&quot;, &amp;SocketAddress, &quot;Int&quot;, SizeOfSocketAddress)
    {
        MsgBox % &quot;connect() indicated Winsock error &quot; . DllCall(&quot;Ws2_32\WSAGetLastError&quot;) . &quot;. Is WinLIRC running?&quot;
        return -1
    }
    return socket  <em>; Indicate success by returning a valid socket ID rather than -1.</em>
}



ReceiveData(wParam, lParam)
<em>; By means of OnMessage(), this function has been set up to be called automatically whenever new data
; arrives on the connection.  It reads the data from WinLIRC and takes appropriate action depending
; on the contents.</em>
{
    Critical  <em>; Prevents another of the same message from being discarded due to thread-already-running.</em>
    socket := wParam
    ReceivedDataSize = 4096  <em>; Large in case a lot of data gets buffered due to delay in processing previous data.</em>

    VarSetCapacity(ReceivedData, ReceivedDataSize, 0)  <em>; 0 for last param terminates string for use with recv().</em>
    ReceivedDataLength := DllCall(&quot;Ws2_32\recv&quot;, &quot;UInt&quot;, socket, &quot;Str&quot;, ReceivedData, &quot;Int&quot;, ReceivedDataSize, &quot;Int&quot;, 0)
    if ReceivedDataLength = 0  <em>; The connection was gracefully closed, probably due to exiting WinLIRC.</em>
        ExitApp  <em>; The OnExit routine will call WSACleanup() for us.</em>
    if ReceivedDataLength = -1
    {
        WinsockError := DllCall(&quot;Ws2_32\WSAGetLastError&quot;)
        if WinsockError = 10035  <em>; WSAEWOULDBLOCK, which means &quot;no more data to be read&quot;.</em>
            return 1
        if WinsockError &lt;&gt; 10054 <em>; WSAECONNRESET, which happens when WinLIRC closes via system shutdown/logoff.</em>
            <em>; Since it's an unexpected error, report it.  Also exit to avoid infinite loop.</em>
            MsgBox % &quot;recv() indicated Winsock error &quot; . WinsockError
        ExitApp  <em>; The OnExit routine will call WSACleanup() for us.</em>
    }
    <em>; Otherwise, process the data received. Testing shows that it's possible to get more than one line</em>
    <em>; at a time (even for explicitly-sent IR signals), which the following method handles properly.</em>
    <em>; Data received from WinLIRC looks like the following example (see the WinLIRC docs for details):</em>
    <em>; 0000000000eab154 00 NameOfButton NameOfRemote</em>
    Loop, parse, ReceivedData, `n, `r
    {
        if A_LoopField in ,BEGIN,SIGHUP,END  <em>; Ignore blank lines and WinLIRC's start-up messages.</em>
            continue
        ButtonName =  <em>; Init to blank in case there are less than 3 fields found below.</em>
        Loop, parse, A_LoopField, %A_Space%  <em>; Extract the button name, which is the third field.</em>
            if A_Index = 3
                ButtonName := A_LoopField
        global DelayBetweenButtonRepeats  <em>; Declare globals to make them available to this function.</em>
        static PrevButtonName, PrevButtonTime, RepeatCount  <em>; These variables remember their values between calls.</em>
        if (ButtonName != PrevButtonName || A_TickCount - PrevButtonTime &gt; DelayBetweenButtonRepeats)
        {
            if IsLabel(ButtonName)  <em>; There is a subroutine associated with this button.</em>
                Gosub %ButtonName%  <em>; Launch the subroutine.</em>
            else <em>; Since there is no associated subroutine, briefly display which button was pressed.</em>
            {
                if (ButtonName == PrevButtonName)
                    RepeatCount += 1
                else
                    RepeatCount = 1
                SplashTextOn, 150, 20, Button from WinLIRC, %ButtonName% (%RepeatCount%)
                SetTimer, SplashOff, 3000  <em>; This allows more signals to be processed while displaying the window.</em>
            }
            PrevButtonName := ButtonName
            PrevButtonTime := A_TickCount
        }
    }
    return 1  <em>; Tell the program that no further processing of this message is needed.</em>
}



SplashOff:
SplashTextOff
SetTimer, SplashOff, Off
return



InsertInteger(pInteger, ByRef pDest, pOffset = 0, pSize = 4)
<em>; The caller must ensure that pDest has sufficient capacity.  To preserve any existing contents in pDest,
; only pSize number of bytes starting at pOffset are altered in it.</em>
{
    Loop %pSize%  <em>; Copy each byte in the integer into the structure as raw binary data.</em>
        DllCall(&quot;RtlFillMemory&quot;, &quot;UInt&quot;, &amp;pDest + pOffset + A_Index-1, &quot;UInt&quot;, 1, &quot;UChar&quot;, pInteger &gt;&gt; 8*(A_Index-1) &amp; 0xFF)
}



ExitSub:  <em>; This subroutine is called automatically when the script exits for any reason.
; MSDN: &quot;Any sockets open when WSACleanup is called are reset and automatically
; deallocated as if closesocket was called.&quot;</em>
DllCall(&quot;Ws2_32\WSACleanup&quot;)
ExitApp
</pre>
</body>
</html>
